ARouter 解析
前言
ARouter 是阿里开源的一款 Android 端路由框架,在近几年掀起的组件化改造浪潮中异军突起,成为了最受开发者欢迎的组件化路由框架。
项目配置
项目结构
除去项目中的示例代码,ARouter 主要分为了四个模块
arouter-annotation
定义注解,包括常用的 Route、Autowired 等,注解的保留时长均为 RetentionPolicy.CLASS,意味着这些注解将会在编译期间通过注解处理器处理,与 butterknife 这个框架的原理类似
arouter-compiler
注解处理器,在 gradle 中通过 annotationProcessor 引入,在编译期间通过 Processor 处理对应的注解生成相应的类文件,在 Android Studio 中可以在 module 下 generatedJava 文件夹内可以看到生成的目标类文件
arouter-api
对外提供的核心 API,框架实际运行的逻辑代码,包括了对生成代码的反射调用
arouter-gradle-plugin
一个锦上添花的 gradle 插件,通过 gradle 的 Transform API 插入一些代码,可以优化 ARouter 初始化时比较多的一些遍历操作,这些都会在编辑期间生成的代码里完成
Compile
一般而言编译器注解会通过 annotationProcessor 生成所需的类,在运行时通过反射实例化,达到目标代码调用的目的。
ARouter 除了被 Deprecated 掉的 Param,还有 Autowired(类似于 Butterknife 的 BindView,自动化注入),Interceptor(用于路由跳转拦截)以及 Route(这个核心,用于标记需要跳转的 Activity,Fragment,Service),在 arouter-compiler 有这三个注解的注解处理器,以最重要的 RouteProcessor 为例
在 process 方法中收集所有的 Route.class 修饰的元素,进过 parseRoutes 方法生成需要的类,我们可以在 generatedJava 目录下查看,由于生成的代码过长且比较繁琐,所以就大致说明一下流程。
每个元素会生成一个 RouteMeta,根据元素路由路径中的 group 存放到 groupMap 全局变量中( Map<String, SetARouter$$Group$${group}
类,遍历玩车后根据 module 名生成 ARouter$$Root$${module}
类作为 group 的引导类。同时,遍历时有注意收集 iProvider 的信息,遍历结束后会生成 ARouter$$Providers$${module}
类作为通过接口查找实例的容器
InterceptorProcessor 会生成包含所有 Interceptor 的 ARouter$$Interceptors$$app
类,AutowiredProcessor 则会在对应包目录下生成 {target}$$ARouter$$Autowired
类,在运行时通过 ARouter.getInstance().inject(this)
注入,原理类似于 butterknife
API
api 模块主要提供对外接口和逻辑实现,这里就主要介绍两个方法,一个是初始化 init 方法,还有一个则是跳转 navigation
ARouter.init
ARouter 是一个装饰类,具体的实现都是由 _ARouter 实现,_ARouter 的 init 方法中,除了 handler 之类的初始化,最重要的则是 LogisticsCenter 的初始化工作了。
1 | public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { |
初始化的主要工作就是扫描安装包,查找生成 compile 生成的类,实例化之后存入到 warehouse 中。虽然做了缓存优化,还是存在着初次加载耗时的问题,所以后期也做了通过编译时代码插入的优化
Warehouse 类里面是一些静态变量,存储着一些路由缓存,以 groupsIndex 为例,ARouter 初始化时就会以 group 为单位存入生成的类,实际路由时才会加载对应的具体的路径 - RouteMeta 键值对
初始化完成之后会执行 afterInit,初始化 interceptorService 这个静态对象,它是所有的拦截器的根实现,过程类似于责任链的模式,可以追踪 /arouter/service/interceptor 这个路径查看实现类,有些可以注意的点,比如说 interceptorsIndex 是一个 TreeMap 可以根据 priority 排序,保证重要性高的先执行,比如说InterceptorServiceImpl.doInterceptions 方法中通过 CancelableCountDownLatch 实现了超时返回
navigation
ARouter.build 通过一个路径生成一个 Postcard 实例,Postcard 继承自 RouteMeta,会暂存一些参数等额外信息,通过调用 navigation 方法实现跳转,里面是的实现则是调用了
ARouter 中 Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback)
这个方法
方法里的代码流程也是十分清晰的
1 | protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { |
这个方法的关键是通过 LogisticsCenter.completion(postcard) 方法通过路径去获取 RouteMeta,这些 RouteMeta 由 compile 生成,注册到 Warehouse 里,在这里才会实例化加载进缓存中,以供这次及以后的调用,而后交由真正的 _navigation 方法执行路由操作,通过 postcard.getType() 获取到的类型执行真正的操作,activity 之类的会进行跳转,其他的会返回实例
Gradle Plugin
配置代码
1 | apply plugin: 'com.alibaba.arouter' |
ARouter 的 init 方法最终会调用 LogisticsCenter 的 init 方法用来初始化,目的就是将生成的相关类实例化,将其中的界面跳转数据以及调用实例之类的存放到 Warehouse 对应的静态容器中,以供后来调转或者调用时查找。
在默认的实现中,会遍历 apk 文件中所有的 dex,查找所有的生成类,但这个过程不仅耗时,还存在不确定性的风险。对于一个全局性的框架来说是不可接受的。
这个插件就是为了解决这个问题而诞生的,Gradle Transform API 可以使工程在编译期间就能找到所有的生成类,然后将这些类实例化注册的代码插入到 LogisticsCenter 的 loadRouterMap 方法中,这个方法则是在 init 方法中会被调用,从而跳过分析 apk 文件来查询 ARouter 的生成类,达到相同的效果。
Gradle Transform API
Android 项目编译时,Gradle 提供了 Transform API 可以让开发者在编译时产生 Class 文件后,并在生成 Dex 之前做一些处理,Transform 会接收 inputs 输入(编译产生的Class文件), 并向已经产生的输入中添加一些东西替换掉这些输入产物,outputs 输出给下一个 Transform 处理,ProGuardTransform 就是系统实现的一个 Transform
ARouter gradlew plugin 的实现原理大致为:
在 AppExtension 里中注册 RegisterTransform 用于处理需求,在 transform 方法中,先遍历 jarInputs 和 directoryInputs(前者为工程 jar 包中的类,包括引用的 aouter-api,后者则为工程中的类),会找到 com/alibaba/android/arouter/core/LogisticsCenter.class 这个类,以及所有 com/alibaba/android/arouter/routes/ 包下的类,即我们生成的存放信息的类,通过 ScanClassVisitor 检查类继承的接口后分类存放。
收集完成后 registerList 会存放所有的相关类路径,遍历时通过 RegisterCodeGenerator 的 insertInitCodeTo 方法将注册方法代码写入到 LogisticsCenter 的 loadRouterMap 方法中,具体的代码如下
1 | @Override |
通过 ASM 的语法,将每个生成类都转换成 register(String className) 方法的调用,追踪一下不难发现,该方法做的是类实例化,存放路由信息,与上文利用 apk 查找类实例的结果一至,同时最后会将 registerByPlugin 标志置为 tru,使得框架初始化时跳过原上文的注册方式
总结
ARouter 的源码阅读并不太难,但是设计的十分优秀,而后又经过了数次迭代优化,行成了如今这个优秀的组件化路由框架